#define	_XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <def.h>
#include <externs.h>

static void forkexec(int cmd_inx);

void
execute_disk_command(void)
{
	//当命令个数为0的时候直接返回，以免误操作
	if(0 == cmd_count)
		return;
	//实现重定向的相关解析
	if('\0' != infile[0]) {
		cmd[0].infd = open(infile,O_RDONLY);
	}
	if('\0' != outfile[0]) {
		if(append) {
			cmd[cmd_count - 1].outfd = open(outfile,
				O_WRONLY | O_CREAT | O_APPEND,0666);
		} else {
			cmd[cmd_count - 1].outfd = open(outfile,
				O_WRONLY | O_CREAT | O_TRUNC,0666);
		}
	}
	//避免僵死进程的存在
	//因为避免僵尸进程关键在于忽略SIGCHLD信号，而僵尸进程发生的情况是子进程退出后要父进程捕获到了子进程的退出信号SIGCHLD才能将其算作真正的结束（这个需要父进程使用wait或者waitpid函数），否则就是僵尸进程。但是父进程在忽略了SIGCHLD的情况下如果使用了wait或者waitpid之类的函数来等待子进程结束由于忽略了信号，那么父进程就阻塞在wait或者waitpid那个地方。
	//这里需要注意的是当这里处理后台进程的时候要忽略SIGCHLD信号，因为后台进程顾名思义就是子进程运行不影响父进程（前台程序一般都要等待其结束后才会切入到shell界面而后台不用）。所以这个时候父进程不能再使用wait的方法来阻塞，那么这个时候子进程运行结束后发出信号，那么父进程就要忽略信号才行，否则子进程就会变成僵尸进程。
	//但是如果是前台的情况，那么就需要恢复信号，因为需要等待退出，否则还是忽略的化就会因为后面的判断而进入wait状态（判断是否是后台执行），程序就阻塞在那里了。
	if(1 == backgnd) {
		signal(SIGCHLD,SIG_IGN);
	} else {
		signal(SIGCHLD,SIG_DFL);
	}
	//let's execute "ls | grep init | wc -w"
	int i,fd;
	int fds[2];
	for(i = 0;i < cmd_count;++i) {
		//如果不是最后一条命令，则需要创建管道
		if(i < cmd_count - 1) {
			pipe(fds);
			//需要注意的是管道的形式，
			//就是把输出和输入定位一下，
			//建立读写入口
			cmd[i].outfd = fds[1];
			cmd[i+1].infd = fds[0];
		}
		forkexec(i);
		//父进程会继续执行下去然后执行前进行进行还原的操作
		if(STDIN_FILENO != (fd = cmd[i].infd))
			close(fd);
		if(STDOUT_FILENO != (fd = cmd[i].outfd))
			close(fd);
	}
	//再进入循环之前将信号的发送定位到伪终端上

	//需要进入循环和伪终端进行交互
	//pty_loop(fdm,1);
	////后台作业的相关处理
	int waitret = 0;
	if(0 == backgnd) {
		////前台作业，就等待最后一个进程退出
		//父进程一定要等待最后一个子进程的退出，否则操作界面就会看起来比较乱
		//
		while(lastpid != (waitret = wait(NULL))) {
			DEBUG_PRINT("lastpid:%d,waitret:%d\n",lastpid,waitret);
			if(waitret < 0) {
				DEBUG_PRINT("wait:%s\n",strerror(errno));
				if(EINTR != errno)
					break;
			}
		}
	}
	//但是这里有一个问题就是后台作业执行的时候不应该有打印输出，
	//而应该在使用bg后在进行打印输出，如果仅仅是立刻模拟bg的输出，会造成界面的混乱。
}

static void 
forkexec(int cmd_inx)
{
	pid_t pid;
	pid = fork();
	if(-1 == pid)
		ERR_EXIT("fork");
	if(pid > 0) {//parent
		if(1 == backgnd)
			printf("%d\n",pid);
		lastpid = pid;
	} else if(0 == pid) {//child
		//backgnd = 1表示将第一条简单命令的infd重定向到/dev/null
		//当第一条命令试图从标准输入获取数据的时候立即返回EOF(ctrl D)
		//标准shell的做法是首先程序进入到后台执行，然后bg键入后立即返回EOF
		//
		if(0 == cmd[cmd_inx].infd && 1 == backgnd) {
			cmd[cmd_inx].infd = open("/dev/null",O_RDONLY);
		}
		//创建一个新的进程组
		//将第一个简单命令进程作为进程组组长
		//成为一个新的会话组长，摆脱原来终端影响
		if(0 == cmd_inx) {
			//setpgid(0,0);
			//看来这里使用setpgid是不够的，应该使用setsid，这个函数将干3件事情
			//1.该进程变成新会话首进程。（会话首进程是创建该会话的进程）此时，该进程是新会话中唯一的进程。
			//2.该进程成为一个心进程组的组长进程。新进程组ID是该调用进程的进程ID。
			//3.该进程没有控制终端。如果在调用setsid之前该进程有一个控制终端，那么这种联系也会被中断。
			pid_t sid = setsid();
			DEBUG_PRINT("sid:%d,pid:%d\n",sid,getpid());
		}
		//这里做的并不完整，因为执行程序后，终端依然是minishell控制着而不是期望运行的程序
		if(STDIN_FILENO != cmd[cmd_inx].infd) {
			DEBUG_PRINT("in file no:%d\n",cmd[cmd_inx].infd);
			close(STDIN_FILENO);
			dup(cmd[cmd_inx].infd);
		}
		if(STDOUT_FILENO != cmd[cmd_inx].outfd) {
			DEBUG_PRINT("out file no:%d\n",cmd[cmd_inx].outfd);
			close(STDOUT_FILENO);
			dup(cmd[cmd_inx].outfd);
		}
		//以前是OPEN_MAX这个宏，但是现在被废弃了，
		//采用sysconf来进行获取
		int open_max = sysconf(_SC_OPEN_MAX);
		for(int i = 3;i < open_max;++i)
			close(i);
		//这里之所以将信号在这里进行还原，是因为在shell
		//状态下不应该响应这些信号，但是一旦切换到了程序
		//中就需要对这些信号进行响应，这里就是对子进程进
		//行处理获得对应的响应。
		if(0 == backgnd) {
			//因为子进程是继承了父进程的信号处理机制，而我们在
			//父进程中为SIGINT和SIGQUIT注册了对应的信号处理
			//函数，那么在子进程中当恢复为默认的信号处理方式，
			//以达到实际的退出效果。
			//还要明确一个概念就是设置进程组对于信号的作用，
			//信号默认是发送给进程组，如果子进程运行的进程组
			//组长不是自己的话就会发送给其它的进程，造成其它
			//进程的误接受。所以创建子进程的时候要确保这个子
			//进程的进程组组长是自己。
			signal(SIGINT,SIG_DFL);
			signal(SIGQUIT,SIG_DFL);
		}
		execvp(cmd[cmd_inx].args[0],cmd[cmd_inx].args);
		DEBUG_PRINT("exit\n");
		exit(EXIT_FAILURE);
	}
}
